package com.awesumgames.awesum;

import java.util.EnumSet;

import javax.vecmath.Matrix4f;
import javax.vecmath.Quat4f;

import com.awesumgames.awesum.AwesumGlobal.Awesum;
import com.awesumgames.awesum.Game;
import com.awesumgames.awesum.Models.Model;
import com.bulletphysics.collision.dispatch.CollisionObject;
import com.bulletphysics.collision.shapes.CollisionShape;
import com.bulletphysics.dynamics.DiscreteDynamicsWorld;
import com.bulletphysics.dynamics.RigidBody;
import com.bulletphysics.dynamics.RigidBodyConstructionInfo;
import com.bulletphysics.linearmath.DefaultMotionState;
import com.bulletphysics.linearmath.MotionState;
import com.bulletphysics.linearmath.Transform;

import android.opengl.GLES20;
import android.opengl.Matrix;

public abstract class Drawable extends RunnableTree {
	public enum TraitFlag { Gl(1), Move(2), Fram(4), Tran(8), Coll(16), Phys(32);
		public int flag = 0;
		TraitFlag(int f) {this.flag = f;}
		public static TraitFlag from(int x) {
	        switch (x) {
		        case 1:
		            return Gl;
		        case 2:
		            return Move;
		        case 4:
		            return Fram;
		        case 8:
		            return Tran;
		        case 16:
		            return Coll;
		        case 32:
		            return Phys;
	        }
	        return null;
	    }
	}
	public EnumSet<TraitFlag> traits(int ts) {
		EnumSet<TraitFlag> traitSet = EnumSet.noneOf(TraitFlag.class);
		for (int i = 0; i < TraitFlag.values().length; i++) {
			int val = TraitFlag.values()[i].flag;
			if ((val & ts) == val)
				traitSet.add(TraitFlag.values()[i]);
		}
		return traitSet;
	}
	public enum CollCheckType { Auto(1), Manual(2);
		public int flag = 0;
		CollCheckType(int f) {this.flag = f;}
	}
	public enum Bounds { X(1), Y(2), Z(3);
		public int flag = 0;
		Bounds(int f) {this.flag = f;}
	}
	
	public Layer3d layer = null;
	
	public TraitGl gl = null;
	public TraitMove move = null;
	//public TraitFram fram = null;
	public TraitTran tran = null;
	public TraitPhys phys = null;

	public Vector3 pos = new Vector3();
	public Vector3 moveTo = new Vector3();
	
	public void add(Drawable d) {
		d.layer = layer;
		children.add(d);
	}
	
	public void load(int traitFlags) {
		EnumSet<TraitFlag> tSet = traits(traitFlags);
		if (tSet.contains(TraitFlag.Gl))	gl = new TraitGl(this);
		if (tSet.contains(TraitFlag.Move))	move = new TraitMove(this);
		if (tSet.contains(TraitFlag.Phys))	phys = new TraitPhys(this);
		
		super.load();
	}
	
	@Override
	public void draw() {
		if (!Game.relative)
			Matrix.setIdentityM(Game.modelMatrix, 0);
		//Matrix.translateM(Game.modelMatrix, 0, pos.x, pos.y, pos.z); //taken out to use bullet getOpenGLMatrix
		
		if (move != null) move.rotate();
		Game.relative = true;
		super.draw(); //draw children relative to this object
		Game.relative = false;
		if (gl != null) {
			gl.transform();
			gl.draw();
		}
	}
	
	@Override
	public void step() {
		if (move != null) move.step();
		else if (tran != null) {
			
		}
		
		if (phys != null) phys.step();
		
		pos.set(moveTo);
		
		super.step();
	}
	
	public void delete() {
		//if (game.scene.children.contains(this))
		//	game.scene.children.remove(this);
		//TODO: NOT scene.children, because this could be the child of a different scene from the topmost.
		//		make it parentScene.delete (this) or whatever
	}
	
	public void collide(Drawable other, Vector3 pointThis, Vector3 pointThat, Vector3 normalThat) {}
	
	private class Trait3d {
		public Drawable parent;
		Trait3d(Drawable dParent) {parent = dParent;}
	}
	/* OpenGL trait */
	public class TraitGl extends Trait3d {
		public Model model = null;
		
		public Vector3	scale = new Vector3(1f, 1f, 1f),
						translate = new Vector3();
		public Quaternion quat = new Quaternion();
		TraitGl(Drawable dParent) {
			super(dParent);
			model = new Models().new Colored();
		}

		public void setDrawModeTriangles() { model.drawMode = GLES20.GL_TRIANGLES; }
		public void setDrawModeTriangleStrip() { model.drawMode = GLES20.GL_TRIANGLE_STRIP; }
		
		public void transform() {
			//Translation
			//Matrix.translateM(Game.modelMatrix, 0, gl.translate.x, gl.translate.y, gl.translate.z);
			//Scaling
			//Matrix.scaleM(Game.modelMatrix, 0, gl.scale.x, gl.scale.y, gl.scale.z);
			//if (!gl.quat.isIdentity()) {
				//Vector3 axis = gl.quat.axis();
				//Matrix.rotateM(Game.modelMatrix, 0, gl.quat.angleDeg(), axis.x, axis.y, axis.z);

				//Matrix.multiplyMM(Game.modelMatrix, 0, Game.modelMatrix, 0, gl.quat.getMatrix(), 0); //this is wrong, but it has a cool paper mario type effect for 3d shit, look into this later
				//Matrix.multiplyMM(Game.modelMatrix, 0, gl.quat.getMatrix(), 0, Game.modelMatrix, 0);
				float[] m = new float[16];
				phys.body.getMotionState().getWorldTransform(new Transform()).getOpenGLMatrix(m);
				Matrix.multiplyMM(Game.modelMatrix, 0, m, 0, Game.modelMatrix, 0);
			//}
		}
		
		public void load() {
			model.load();
		}
		public void draw() {
			model.draw();
		}
	}
	/* Move trait */
	public class TraitMove extends Trait3d {
		public float	speed = 0f;
		public boolean	drawRotation = false;	//whether the object's rotation for movement also gets drawn
		public Vector3	velocity = new Vector3(),
						acceleration = new Vector3();
		
		//TODO: make a class for this stuff so there isn't wasted space on refs for non-followers
		private Drawable following = null;
		private Vector3 followOffset = null;
		private boolean followRot = false;
		private boolean followRotThis = false;
		private float followDist = 0f;
		
		public Quaternion rot = new Quaternion();
		TraitMove(Drawable dParent) {
			super(dParent);
		}
		
		public void rotate() {
			if (move.drawRotation) {
				if (!move.rot.isIdentity()) {
					Vector3 axis = move.rot.axis();
					Matrix.rotateM(Game.modelMatrix, 0, move.rot.angleDeg(), axis.x, axis.y, axis.z);
				}
			}
		}
		
		public void step() {
			Vector3 v;
			if (following != null) {
				float dist = parent.pos.distFrom(following.pos);
				float newDist;
				if (dist > followDist) {
					newDist = followDist;
				} else {
					newDist = dist * .9f;
				}
				Vector3 diff = parent.pos.copy().subtract(following.pos);
				float a = (float)Math.atan2(diff.x, diff.z);
				float hyp = (float)Math.sqrt(diff.x * diff.x + diff.z * diff.z);
				float yAng = (float)Math.atan2(diff.y, hyp);
				v = new Vector3(
					parent.pos.x = following.pos.x + newDist * (float)Math.sin(a) * (float)Math.cos(yAng),
					parent.pos.z = following.pos.z + newDist * (float)Math.cos(a) * (float)Math.cos(yAng),
					parent.pos.y = following.pos.y + newDist * (float)Math.sin(yAng)
				);
			} else {
				velocity.add(acceleration);
				//v = velocity.copy();
				
				//if (!move.rotation.equals(0f, 0f, 0f)) {
				//TODO: this operation should have some sort of flag to determine if this is how
				//it should work. This is ideal for the car game, to proceed in the new move direction
				//with the same velocity, but won't be appropriate for everything.
				//Vector3 a = move.acceleration.copy();	//?
				//rot.rotate(a);				//?
				v = move.rot.rotate(velocity);
				//}
			}
			moveTo = pos.copy().add(v);
		}
		public void follow(Drawable d2, boolean followRotation, boolean rotateThis) {
			follow(d2, followRotation, rotateThis, new Vector3());
		}
		public void follow(Drawable d2, boolean followRotation, boolean rotateThis, Vector3 offset) {
			following = d2;
			followRot = followRotation;
			followRotThis = rotateThis;
			followOffset = offset;
		}
		
		/*public void updateRotation() {
			if (useQuat)
				rotation = quat.getEulerDeg().add(360).mod(360);
		}*/
	}
	/* Transitions trait */
	public class TraitTran extends Trait3d {
		TraitTran(Drawable dParent) {
			super(dParent);
		}
	}
	/* Physics trait */
	public class TraitPhys extends Trait3d {
		public boolean moveManual = false;	//if true, you set object's pos vector manually
		public boolean receiveCollisions = true;
		public RigidBody body = null;
		public CollisionShape collShape = null;
		TraitPhys(Drawable dParent) {
			super(dParent);
		}
		
		public void init(float mass, float restitution, short maskType, short maskCollideWith) {
			//mass in KG
			if (collShape == null) return;
			
			Awesum.debug("initting phys obj at " + parent.pos.toString());

			Transform transform = new Transform(new Matrix4f(new Quat4f(parent.gl.quat.x, parent.gl.quat.y, parent.gl.quat.z, parent.gl.quat.w), parent.pos.copy(), 1f));
			//Transform transform = new Transform(new Matrix4f(new Quat4f(0,0,0,1), parent.pos.copy(), 1f));
			MotionState ms = new DefaultMotionState(transform);
			Vector3 inertia = new Vector3(0f, 0f, 0f);
			collShape.calculateLocalInertia(mass, inertia);
			RigidBodyConstructionInfo rbci = new RigidBodyConstructionInfo(mass, ms, collShape, inertia);
			rbci.restitution = restitution;
			body = new RigidBody(rbci);
			body.setUserPointer(parent);
			body.setActivationState(CollisionObject.DISABLE_DEACTIVATION);
			Game.phys.world.addRigidBody(body, maskType, maskCollideWith);
			//rbci.angularDamping
		}
		
		public void manualTransformUpdate() {
			body.setWorldTransform(new Transform(new Matrix4f(new Quat4f(parent.gl.quat.x, parent.gl.quat.y, parent.gl.quat.z, parent.gl.quat.w), parent.pos.copy(), 1f)));
		}
		
		public void step() {
			if (body != null) {
				if (moveManual) {
					Transform t = new Transform();
					t = body.getCenterOfMassTransform(t);
					t.origin.set(parent.pos);
					t.setRotation(new Quat4f(parent.gl.quat.x, parent.gl.quat.y, parent.gl.quat.z, parent.gl.quat.w));
					body.setCenterOfMassTransform(t);
				} else {
					parent.moveTo.set(body.getWorldTransform(new Transform()).origin);
					//parent.gl.quat.set(body.getWorldTransform(new Transform()).getRotation(new Quat4f()));
				}
				//Awesum.debug("Physics item step");
			}
			//if (parent.pos.y > 8f)
				//Awesum.debug("moveTo = " + parent.moveTo.toString());
		}
	}
}
